package se.webbzon.boltzmann.input;

import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.event.MouseInputListener;

public class KeyboardInput extends Input implements KeyListener, MouseInputListener {
	
	// A map containing all keyboard inputs and button enumerations
	private final Map<Integer,Button> keyMap;
	
	// A map containing information about whatever a key is pressed
	private final Map<Button,Boolean> pressedMap;
	
	// A map containing information about the keys
	private final Map<Button,ButtonInfo> infoMap;
	
	// The point from which the mouse has been dragged
	private Point dragFrom;
	
	// The button to bind via the next key event
	private Button binding;
	
	/*============================================================
	Constructor
	============================================================*/
	
	public KeyboardInput() {
		keyMap = new HashMap<Integer,Button>(16);
		pressedMap = new HashMap<Button,Boolean>(16);
		infoMap = new HashMap<Button,ButtonInfo>(16);
		initializeKeyMap();
		binding = null;
	}
	
	/*============================================================
	Public Methods
	============================================================*/

	@Override public String getName() {
		return "Keyboard & Mouse";
	}

	@Override public synchronized ButtonInfo getButtonInfo(Button button) {
		return infoMap.get(button);
	}
	
	@Override public synchronized boolean supportsBinding(Button button) {
		return true;
	}

	@Override public synchronized boolean startBinding(Button button) {
		if (binding != null)
			return false;
		else {
			binding = button;
			return true;
		}
	}

	@Override public synchronized boolean stopBindings() {
		binding = null;
		return true;
	}

	@Override public void keyPressed(KeyEvent keyEvent) {
		int keyCode = keyEvent.getKeyCode();
		Button binding = getBinding();
		if (binding != null) {
			bindKey(keyCode,binding);
			stopBindings();
			acceptBinding(binding);
		} else {
			Button button = keyMap.get(keyCode);
			if (button != null && !pressedMap.get(button)) {
				pressedMap.put(button,true);
				pressButton(button);
				updatePrimaryAxis();
			}
		}
	}

	@Override public void keyReleased(KeyEvent keyEvent) {
		int keyCode = keyEvent.getKeyCode();
		Button button = keyMap.get(keyCode);
		if (button != null && pressedMap.get(button)){
			pressedMap.put(button,false);
			releaseButton(button);
			updatePrimaryAxis();
		}
	}

	@Override public void keyTyped(KeyEvent keyEvent) {};
	
	@Override public void mouseClicked(MouseEvent mouseEvent) {};

	@Override public void mouseEntered(MouseEvent mouseEvent) {};

	@Override public void mouseExited(MouseEvent mouseEvent) {};

	@Override public void mousePressed(MouseEvent mouseEvent) {
		if (mouseEvent.getButton() == MouseEvent.BUTTON1)
			dragFrom = mouseEvent.getPoint();
	}

	@Override public void mouseReleased(MouseEvent mouseEvent) {
		setSecondaryAxis(0,0);
		dragFrom = null;
	}
	
	@Override public void mouseDragged(MouseEvent mouseEvent) {
		if (dragFrom != null) {
			double x = mouseEvent.getX() - dragFrom.x;
			double y = dragFrom.y - mouseEvent.getY();
			double direction = Math.atan2(y, x);
			double value = Math.min(1,(Math.hypot(x, y)/160.0));
			setSecondaryAxis(direction,value);
		}
	}

	@Override public void mouseMoved(MouseEvent mouseEvent) {};
	
	/** Releases all the keys by resetting the input memory. **/
	public void releaseAll() {
		Collection<Button> buttons = keyMap.values();
		for (Button b : buttons)
			pressedMap.put(b, false);
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Returns the button associated with the last unhandled binding call.
	 * If there is no unhandled binding call then null is returned. **/
	private synchronized Button getBinding() {
		return binding;
	}
	
	/** Initializes the key map. **/
	private void initializeKeyMap() {
		bindKey(KeyEvent.VK_A, Button.LEFT);
		bindKey(KeyEvent.VK_D, Button.RIGHT);
		bindKey(KeyEvent.VK_W, Button.UP);
		bindKey(KeyEvent.VK_S, Button.DOWN);
		
		bindKey(KeyEvent.VK_SPACE, Button.JUMP);
		bindKey(KeyEvent.VK_Q, Button.SWITCH);
		bindKey(KeyEvent.VK_E, Button.SPECIAL);
		bindKey(KeyEvent.VK_F, Button.ACTION);
		
		bindKey(KeyEvent.VK_R, Button.INVENTORY);
		bindKey(KeyEvent.VK_1, Button.PREVIOUS_ITEM);
		bindKey(KeyEvent.VK_2, Button.NEXT_ITEM);
		bindKey(KeyEvent.VK_3, Button.PREVIOUS_CHARACTER);
		bindKey(KeyEvent.VK_4, Button.NEXT_CHARACTER);
	}
	
	/** Binds the given KeyEvent key code to the provided button. **/
	private void bindKey(int keyCode, Button button) {
		// Unbind old key
		Integer oldKey = null;
		for (Entry<Integer,Button> entry : keyMap.entrySet()) {
			if (entry.getValue() == button) {
				oldKey = entry.getKey();
				break;
			}
		}
		if (oldKey != null)
			keyMap.remove(oldKey);
		
		// Bind new key
		keyMap.put(keyCode, button);
		pressedMap.put(button, false);
		infoMap.put(button, new KeyboardButtonInfo(keyCode));
	}
	
	/** Updates the primary axis. **/
	private void updatePrimaryAxis() {
		final boolean 	left = pressedMap.get(Button.LEFT),
						right = pressedMap.get(Button.RIGHT),
						up = pressedMap.get(Button.UP),
						down = pressedMap.get(Button.DOWN);
		if (left || right || up || down) {
			final double 	leftd = left ? 1.0 : 0.0,
							rightd = right ? 1.0 : 0.0, 
							upd = up ? 1.0 : 0.0, 
							downd = down ? 1.0 : 0.0;
			double direction = Math.atan2(upd-downd, rightd-leftd);
			if (direction < 0)
				direction += 2*Math.PI;
			setPrimaryAxis(direction,1);
		} else
			setPrimaryAxis(0,0);
	}

}
